#include <iostream>
#include <csignal>
#include <sys/socket.h>
#include <netinet/in.h>
#include <string.h>
#include <arpa/inet.h>
#include <sys/select.h>
#include <unistd.h>

using namespace std;

const uint32_t FD_SETS = FD_SETSIZE;

void close_app(int s) {
    cout << s << endl;
    exit(0);
}

// doip
// socket -> bind -> listen -> accept -> read/write/close
// socket -> connect -> read -> write -> close

static void acceptClient(int *clientsSocket, int listenSocket) {
    // 6.成功返回客户端socket，失败返回-1，如果socket是阻塞，则accept阻塞，否则非阻塞；默认是阻塞模式
    int clientFd = accept(listenSocket, nullptr, nullptr); // sys/socket.h

    if(clientFd < 0) {
        cout << "accept failed" << endl;
        return;
    }

    cout << "accept success" << endl;

    size_t i=0;
    for(; i<FD_SETS; ++i) {
        if(clientsSocket[i] == -1) {
            clientsSocket[i] = clientFd;
            break;
        }
    }
    if(i == FD_SETS) {
        close(clientFd);
        cout << "too many clients" << endl;
    }
}

static void handleClientMsg(int fd, char *buf) {
    assert(buf);
    cout << "recv : " << buf << endl;
    write(fd, buf, strlen(buf));
}

static void recvClientMsg(int *clientsSocket, fd_set *fdSets) {
    char buf[1024] = {0};
    for(size_t i=0; i<FD_SETS; ++i) {
        if(clientsSocket[i] == -1) {
            continue;
        } else if (FD_ISSET(clientsSocket[i], fdSets)) {
            int n = read(clientsSocket[i], buf, 1024);
            if(n <= 0) {
                FD_CLR(clientsSocket[i], fdSets);
                close(clientsSocket[i]);
                clientsSocket[i] = -1;
                continue;
            }
            handleClientMsg(clientsSocket[i], buf);
        }
    }
}

int main() {

    std::signal(SIGINT, close_app);
    
    // 1.创建监听socket，AD_INET为ipv4协议簇，ip地址类型，SOCK_STREAM为面向连接的数据传输方式，0代表根据参数自动选择协议，sys/socket.h
    int listenSocket = socket(AF_INET, SOCK_STREAM, 0); // 参考：https://xiaolincoding.com/os/4_process/process_commu.html#socket
    
    // 2.绑定ip:port到socket上，处理网络通信有两个地址（结构体）：sockaddr sys/socket.h 和 sockaddr_in netinet/in.h，第二个为解决第一个将port和地址混在一起的缺点
    struct sockaddr_in serverAddress; // 参考：https://www.cnblogs.com/cyx-b/p/12450811.html 
    // memset(&serverAddress, 0, sizeof(serverAddress)); // 参考 reference
    memset_s(&serverAddress, sizeof(serverAddress), 0, sizeof(serverAddress)); // 内存块指针，内存块区域大小（第四个参数超出此参数即报错），操作成功返回0，错误返回非0

    struct in_addr ipAddr;
    inet_aton("127.0.0.1", &ipAddr); // arpa/inet.h

    const uint16_t port = 13400;

    serverAddress.sin_family = AF_INET; // 2字节，地址簇
    serverAddress.sin_addr = ipAddr; // 4字节，内部为网络字节序，低位数据存储在高地址
    serverAddress.sin_port = htons(port); // 2字节，host to network short，类似的 htonl host to network long

    // 成功返回0，失败返回-1，并设置errno来指示具体的错误
    // 使用c++风格指针类型转换，不执行运行时的类型检查，而进行编译时的类型检查
    // sys/socket.h
    if(::bind(listenSocket, reinterpret_cast<struct sockaddr*>(&serverAddress), sizeof(struct sockaddr_in)) < 0) {
        cerr << "bind error" << endl;
        return -1;
    }
    cout << "Server started. Waiting for connections..." << endl;

    // 3.监听socket sys/socket.h
    // backlog: 已完成的连接但服务器还未处理，或正在进行的连接，的数量
    const int backlog = 10;
    if(listen(listenSocket, backlog) < 0) { // 参考：https://bbs.huaweicloud.com/blogs/287831
        cerr << "listen error" << endl;
        return -1;
    }
    // listen后，服务器进入listen状态

    // 4.select，准备1024bit的文件描述符集合，以及1024大小的数组存储客户端socket
    fd_set fdSets; // 数组大小为32，每个元素32位，总共1024位
    struct timeval timeVal;
    timeVal.tv_sec = 5;
    timeVal.tv_usec = 0;

    int clientsSocket[FD_SETSIZE]; 
    memset(clientsSocket, -1, FD_SETSIZE * sizeof(int)); // 字节操作！！！

    cout << "listenSocket: " << listenSocket << endl;
    int maxSocket = -1;
    for (;;) { // 参考：https://www.geeksforgeeks.org/tcp-and-udp-server-using-select/ 和 https://github.com/xiaofannn/socket/blob/master/select.cpp#L21
        FD_ZERO(&fdSets);
        FD_SET(listenSocket, &fdSets);
        maxSocket = listenSocket;

        for(size_t i=0; i<FD_SETSIZE; ++i) {
            if(clientsSocket[i] != -1) {
                FD_SET(clientsSocket[i], &fdSets);
                maxSocket = clientsSocket[i] > maxSocket ? clientsSocket[i] : maxSocket;
            }
        }

        // 5.将文件描述符拷贝进内核，writefds设置为nullptr代表不关心socket是否可写，timeout设置为nullptr，代表立即返回，sys/select.h
        int nReady = select(maxSocket + 1, &fdSets, nullptr, nullptr, &timeVal); // 参考：https://learn.microsoft.com/en-us/windows/win32/api/winsock2/nf-winsock2-select 和 https://wuxiaoleisuperlei.github.io/2017/12/22/select/
        if(nReady < 0) {
            cerr << "select error" << endl;
            return -1;
        }
        cout << "nReady: " << nReady << endl;

        // 6.查询文件描述符集合，是否有新建立的连接内部调用accept函数
        if(FD_ISSET(listenSocket, &fdSets)) {
            acceptClient(clientsSocket, listenSocket);
            cout << "accept client" << endl;
        }
        
        // 7.处理客户端消息
        recvClientMsg(clientsSocket, &fdSets);

    }
    return 0;
}



